﻿using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Media;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Tax;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Logistics;
using Nop.Services.Media;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Seo;
using Nop.Services.Tax;
using Nop.Web.Extensions;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Security;
using Nop.Web.Infrastructure.Cache;
using Nop.Web.Models.Checkout;
using Nop.Web.Models.Common;
using Nop.Web.Models.Order;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Nop.Core.Domain.Logistics;

namespace Nop.Web.Controllers
{
    public partial class ReturnRequestController : BaseNopController
    {
        #region Fields

        private readonly IOrderService _orderService;
        private readonly IWorkContext _workContext;
        private readonly ICurrencyService _currencyService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly IProductService _productService;
        private readonly ILocalizationService _localizationService;
        private readonly ICustomerService _customerService;
        private readonly IWorkflowMessageService _workflowMessageService;
        private readonly ICacheManager _cacheManager;
        private readonly LocalizationSettings _localizationSettings;
        private readonly OrderSettings _orderSettings;
        private readonly Nop.Services.Orders.IOrderProcessingService _orderProcessingOrdersService;
        private readonly Nop.Services.Logistics.IOrderProcessingService _orderProcessingLogisticsService;
        private readonly IWebHelper _webHelper;
        private readonly IPictureService _pictureService;
        private readonly IAddressService _addressService;
        private readonly AddressSettings _addressSettings;
        private readonly IStateProvinceService _stateProvinceService;
        private readonly ICountryService _countryService;
        private readonly MediaSettings _mediaSettings;
        private readonly Nop.Services.Logistics.IOrderProcessingService _orderProcessingService;
        private readonly ILogisticsService _logisticsService;
        private readonly HttpContextBase _httpContext;
        private readonly RewardPointsSettings _rewardPointsSettings;
        private readonly IOrderTotalCalculationService _orderTotalCalculationService;
        private readonly PaymentSettings _paymentSettings;
        private readonly IPaymentService _paymentService;
        private readonly ITaxService _taxService;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly ILogger _logger;
        private readonly IMobileDeviceHelper _mobileDeviceHelper;
        private readonly IInvoiceService _invoiceService;

        #endregion

        #region Constructors

        public ReturnRequestController(IOrderService orderService,
            IWorkContext workContext, IWebHelper webHelper,
            ICurrencyService currencyService, IPriceFormatter priceFormatter,
            Nop.Services.Orders.IOrderProcessingService orderProcessingOrdersService,
            Nop.Services.Logistics.IOrderProcessingService orderProcessingLogisticsService,
            ILocalizationService localizationService, IProductService productService,
            ICustomerService customerService, ICacheManager cacheManager,
            IWorkflowMessageService workflowMessageService, IPictureService pictureService,
            LocalizationSettings localizationSettings, MediaSettings mediaSettings,
            OrderSettings orderSettings, AddressSettings addressSettings, IStateProvinceService stateProvinceService
            , ICountryService countryService, Nop.Services.Logistics.IOrderProcessingService orderProcessingService,
            ILogisticsService logisticsService, HttpContextBase httpContext, IAddressService addressService
            , RewardPointsSettings rewardPointsSettings, IOrderTotalCalculationService orderTotalCalculationService,
            PaymentSettings paymentSettings, IPaymentService paymentService, ITaxService taxService,
            IGenericAttributeService genericAttributeService, ILogger logger, IMobileDeviceHelper mobileDeviceHelper,
            IInvoiceService invoiceService)
        {
            this._orderService = orderService;
            this._workContext = workContext;
            this._currencyService = currencyService;
            this._productService = productService;
            this._priceFormatter = priceFormatter;
            this._orderProcessingOrdersService = orderProcessingOrdersService;
            this._orderProcessingLogisticsService = orderProcessingLogisticsService;
            this._localizationService = localizationService;
            this._customerService = customerService;
            this._pictureService = pictureService;
            this._workflowMessageService = workflowMessageService;
            this._cacheManager = cacheManager;
            this._localizationSettings = localizationSettings;
            this._orderSettings = orderSettings;
            this._addressSettings = addressSettings;
            this._stateProvinceService = stateProvinceService;
            this._countryService = countryService;
            this._orderProcessingService = orderProcessingService;
            this._logisticsService = logisticsService;
            this._httpContext = httpContext;
            this._webHelper = webHelper;
            this._mediaSettings = mediaSettings;
            this._addressService = addressService;
            this._rewardPointsSettings = rewardPointsSettings;
            this._orderTotalCalculationService = orderTotalCalculationService;
            this._paymentSettings = paymentSettings;
            this._paymentService = paymentService;
            this._taxService = taxService;
            this._genericAttributeService = genericAttributeService;
            this._logger = logger;
            this._mobileDeviceHelper = mobileDeviceHelper;
            this._invoiceService = invoiceService;
        }

        #endregion

        #region Utilities

        [NonAction]
        protected bool IsMinimumOrderPlacementIntervalValid(Customer customer)
        {
            //prevent 2 orders being placed within an X seconds time frame
            if (_orderSettings.MinimumOrderPlacementInterval == 0)
                return true;

            var lastOrderPlaced = _orderService.GetOrdersByCustomerId(_workContext.CurrentCustomer.Id)
                .FirstOrDefault();
            if (lastOrderPlaced == null)
                return true;

            var interval = DateTime.UtcNow - lastOrderPlaced.CreatedOnUtc;
            return interval.TotalSeconds > _orderSettings.MinimumOrderPlacementInterval;
        }

        [NonAction]
        protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequestModel model, Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            if (model == null)
                throw new ArgumentNullException("model");

            model.OrderId = order.Id;

            //return reasons
            if (_orderSettings.ReturnRequestReasons != null)
                foreach (var rrr in _orderSettings.ReturnRequestReasons)
                {
                    model.AvailableReturnReasons.Add(new SelectListItem()
                    {
                        Text = rrr,
                        Value = rrr
                    });
                }

            //return actions
            if (_orderSettings.ReturnRequestActions != null)
                foreach (var rra in _orderSettings.ReturnRequestActions)
                {
                    model.AvailableReturnActions.Add(new SelectListItem()
                    {
                        Text = rra,
                        Value = rra
                    });
                }

            if (model.ShipmentMethods.Count == 0)
            {
                model.ShipmentMethods.Add(new SelectListItem() { Text = _localizationService.GetResource("commerce.return.shipment.spotshubexpress"), Value = "1", Selected = model.ShipmentMethodId > 0 ? (model.ShipmentMethodId == 1 ? true : false) : true });
                model.ShipmentMethods.Add(new SelectListItem() { Text = _localizationService.GetResource("commerce.return.shipment.3rdparty"), Value = "2", Selected = model.ShipmentMethodId > 0 && model.ShipmentMethodId == 2 });
            }

            //products
            var orderProductVariants = _orderService.GetAllOrderProductVariants(order.Id, null, null, null, null, null, null).Where(opv => !opv.RefundQuantity.HasValue || opv.Quantity > opv.RefundQuantity.Value);
            foreach (var opv in orderProductVariants)
            {
                var opvModel = new SubmitReturnRequestModel.OrderProductVariantModel()
                {
                    Id = opv.Id,
                    ProductId = opv.ProductVariant.ProductId,
                    ProductSeName = opv.ProductVariant.Product.GetSeName(),
                    AttributeInfo = opv.AttributeDescription,
                    Quantity = opv.RefundQuantity.HasValue ? (opv.Quantity - opv.RefundQuantity.Value) : opv.Quantity,
                };
                int pictureSize = _mediaSettings.MiniCartThumbPictureSize;
                var product = _productService.GetProductById(opv.ProductVariant.ProductId);
                var defaultProductPictureCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_DEFAULTPICTURE_MODEL_KEY, opv.ProductVariant.ProductId, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                opvModel.ProductPicture = _cacheManager.Get(defaultProductPictureCacheKey, () =>
                {
                    var picture = product.GetDefaultProductPicture(_pictureService);
                    var pictureModel = new Nop.Web.Models.Media.PictureModel()
                    {
                        ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize),
                        FullSizeImageUrl = _pictureService.GetPictureUrl(picture),
                        Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), product.Name),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), product.Name)
                    };
                    return pictureModel;
                });

                //product name
                if (!String.IsNullOrEmpty(opv.ProductVariant.GetLocalized(x => x.Name)))
                    opvModel.ProductName = string.Format("{0} ({1})", opv.ProductVariant.Product.GetLocalized(x => x.Name), opv.ProductVariant.GetLocalized(x => x.Name));
                else
                    opvModel.ProductName = opv.ProductVariant.Product.GetLocalized(x => x.Name);
                model.Items.Add(opvModel);

                //unit price
                switch (order.CustomerTaxDisplayType)
                {
                    case TaxDisplayType.ExcludingTax:
                        {
                            var opvUnitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceExclTax, order.CurrencyRate);
                            opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false);
                        }
                        break;
                    case TaxDisplayType.IncludingTax:
                        {
                            var opvUnitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(opv.UnitPriceInclTax, order.CurrencyRate);
                            opvModel.UnitPrice = _priceFormatter.FormatPrice(opvUnitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, true);
                        }
                        break;
                }
            }

            return model;
        }


        private void ClearPendingReturnRequests(Customer customer = null)
        {
            if (customer == null)
                customer = _workContext.CurrentCustomer;
            var pendingReturnRequestAttribute = _genericAttributeService.GetAttribute(customer.Id, "Customer", SystemCustomerAttributeNames.PendingReturnRequests);
            if (pendingReturnRequestAttribute != null)
            {
                _genericAttributeService.DeleteAttribute(pendingReturnRequestAttribute);
            }
        }

        private List<ReturnRequest> GetPendingReturnRequests(Customer customer = null)
        {
            var pendingReturnRequests = new List<ReturnRequest>();
            if (customer == null)
                customer = _workContext.CurrentCustomer;
            var pendingReturnRequestAttribute = _genericAttributeService.GetAttribute(customer.Id, "Customer", SystemCustomerAttributeNames.PendingReturnRequests);
            if (pendingReturnRequestAttribute != null)
            {
                try
                {
                    var pendingReturnRequestsList = XmlHelper.Deserialize<List<ReturnRequest>>(pendingReturnRequestAttribute.Value);
                    if (pendingReturnRequestsList != null)
                    {
                        pendingReturnRequests = pendingReturnRequestsList;
                    }
                }
                catch (Exception e)
                {
                    _logger.Error("Unable to determine pending return requests.", e, customer);
                    _genericAttributeService.DeleteAttribute(pendingReturnRequestAttribute);
                }
                
            }
            return pendingReturnRequests;
        }

        #endregion

        #region Return requests

        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult ReturnRequest(int orderId)
        {
            var order = _orderService.GetOrderById(orderId);
            var customer = _workContext.CurrentCustomer;
            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            if (!_orderProcessingOrdersService.IsReturnRequestAllowed(order))
                return RedirectToRoute("HomePage");

            var model = new SubmitReturnRequestModel();

            model.ShipmentMethods.Add(new SelectListItem() { Text = _localizationService.GetResource("commerce.return.shipment.spotshubexpress"), Value = "1", Selected = true });
            model.ShipmentMethods.Add(new SelectListItem() { Text = _localizationService.GetResource("commerce.return.shipment.3rdparty"), Value = "2", Selected = false });

            model = PrepareReturnRequestModel(model, order);
            if (model.Items == null || model.Items.Count == 0)
            {
                ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.AllItemsReturned"));
                return RedirectToAction("Details", "Order", new { orderId = orderId });
            }

            return View(model);
        }

        [HttpPost]
        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult AjaxReturnRequest(int OrderId)
        {
            var order = _orderService.GetOrderById(OrderId);
            var customer = _workContext.CurrentCustomer;
            var html = "";
            var message = "";
            ModelState.Clear();

            var returnrequests = GetPendingReturnRequests(customer);
            ClearPendingReturnRequests(customer);

            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                ModelState.AddModelError("", "Request denied. You cannot request a return for this order.");

            if (!_orderProcessingOrdersService.IsReturnRequestAllowed(order))
                ModelState.AddModelError("", "Request denied. This order is currently not open for return request.");
            if (ModelState.IsValid)
            {
                var model = new SubmitReturnRequestModel();
                
                model = PrepareReturnRequestModel(model, order);
                if (model.Items == null || model.Items.Count == 0)
                {
                    ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.AllItemsReturned"));
                }
                else
                {
                    html = this.RenderPartialViewToString("AjaxReturnRequest", model);
                }
                
            }

            ModelStateErrorNotification();
            message = RenderAjaxNotification();
            
            return Json(new { 
                html = html,
                message = message,
                success = ModelState.IsValid
            });
        }

        [HttpPost]
        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult AjaxReturnRequestInit(SubmitReturnRequestModel model, int OrderId, FormCollection form)
        {
            var customer = _workContext.CurrentCustomer;
            var order = _orderService.GetOrderById(OrderId);
            var html = "";
            var message = "";

            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                ModelState.AddModelError("", "Request denied. You cannot request a return for this order.");

            if (!_orderProcessingOrdersService.IsReturnRequestAllowed(order))
                ModelState.AddModelError("", "Request denied. This order is currently not open for return request.");

            model = PrepareReturnRequestModel(model, order);
            if (model.Items == null || model.Items.Count == 0)
            {
                ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.AllItemsReturned"));
            }

            if (!ModelState.IsValid)
            {
                ModelStateErrorNotification();
                message = RenderAjaxNotification();
                html = this.RenderPartialViewToString("AjaxReturnRequest", PrepareReturnRequestModel(model, order));
                return Json(new
                {
                    html = html,
                    message = message,
                    success = false
                });
            }

            int count = 0;
            var rrs = new List<ReturnRequest>();
            if (!(model.ShipmentMethodId > 0))
            {
                ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.ShippingMethodRequired"));
            }
            foreach (var opv in order.OrderProductVariants)
            {
                int quantity = 0; //parse quantity
                foreach (string formKey in form.AllKeys)
                    if (formKey.Equals(string.Format("quantity-{0}", opv.Id), StringComparison.InvariantCultureIgnoreCase))
                    {
                        int.TryParse(form[formKey], out quantity);
                        break;
                    }
                if (quantity > 0)
                {
                    if (model.Items.FirstOrDefault(i => i.Id == opv.Id) != null)
                    {
                        rrs.Add(new ReturnRequest()
                        {
                            OrderProductVariantId = opv.Id,
                            Quantity = quantity,
                            CustomerId = _workContext.CurrentCustomer.Id,
                            ReasonForReturn = model.ReturnReason,
                            RequestedAction = model.ReturnAction,
                            CustomerComments = model.Comments,
                            StaffNotes = string.Empty,
                            ReturnRequestStatus = ReturnRequestStatus.Pending,
                            CreatedOnUtc = DateTime.UtcNow,
                            UpdatedOnUtc = DateTime.UtcNow
                        });

                        model.Items.FirstOrDefault(i => i.Id == opv.Id).ReturnQuantity = quantity;
                        //refund opv
                        //opv.RefundQuantity = opv.RefundQuantity.HasValue ? (opv.RefundQuantity.Value + quantity) : quantity;
                        count++;
                    }
                }
            }

            if (count <= 0)
            {
                ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.NoItemsSubmitted"));
            }
            else
            {
                _genericAttributeService.SaveAttribute<List<ReturnRequest>>(customer, SystemCustomerAttributeNames.PendingReturnRequests, rrs);
                //rrs.ForEach(x => _workContext.CurrentCustomer.ReturnRequests.Add(x));
                //_customerService.UpdateCustomer(_workContext.CurrentCustomer);
                html = this.RenderPartialViewToString("AjaxReturnRequestConfirm", model);
            }
            
            if (!ModelState.IsValid)
            {
                ModelStateErrorNotification();
                message = RenderAjaxNotification();
                html = this.RenderPartialViewToString("AjaxReturnRequest", model);
            }
            return Json(new
            {
                html = html,
                message = message,
                success = true,
                final = ModelState.IsValid
            });
        }

        [HttpPost]
        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult AjaxReturnRequestConfirm(int OrderId)
        {
            var customer = _workContext.CurrentCustomer;
            var order = _orderService.GetOrderById(OrderId);
            var html = "";
            var message = "";

            ModelState.Clear();

            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                ModelState.AddModelError("", "Request denied. You cannot request a return for this order.");

            if (!_orderProcessingOrdersService.IsReturnRequestAllowed(order))
                ModelState.AddModelError("", "Request denied. This order is currently not open for return request.");

            var returnRequests = GetPendingReturnRequests(customer);
            if (returnRequests == null || returnRequests.Count == 0)
                ModelState.AddModelError("", "Request denied. Either this request does not exist, or no item is requested for a return.");

            var model = new SubmitReturnRequestModel();
            PrepareReturnRequestModel(model, order);
            var items = model.Items;
            if (items == null || items.Count == 0)
            {
                ModelState.AddModelError("", _localizationService.GetResource("ReturnRequests.AllItemsReturned"));
            }

            if (ModelState.IsValid)
            {
                // Compare available items
                foreach (var r in returnRequests)
                {
                    var opv = order.OrderProductVariants.FirstOrDefault(opvs => opvs.Id == r.OrderProductVariantId && (opvs.RefundQuantity.HasValue ? opvs.RefundQuantity : 0) + r.Quantity <= opvs.Quantity);
                    if (opv == null)
                    {
                        ModelState.AddModelError("", "Request denied. The request contains unmatched items or return quantity.");
                        break;
                    }
                }
            }
            if (!ModelState.IsValid) {
                ModelStateErrorNotification();
                message = RenderAjaxNotification();
                return Json(new
                {
                    html = html,
                    message = message,
                    success = false
                });
            }

            // All verified, place return request, update and refresh
            foreach (var r in returnRequests)
            {
                var opv = order.OrderProductVariants.FirstOrDefault(opvs => opvs.Id == r.OrderProductVariantId);
                opv.RefundQuantity = opv.RefundQuantity.HasValue ? (opv.RefundQuantity.Value + r.Quantity) : r.Quantity;
                _workContext.CurrentCustomer.ReturnRequests.Add(r);
            }
            _customerService.UpdateCustomer(_workContext.CurrentCustomer);
            _orderService.UpdateOrder(order);
            ClearPendingReturnRequests();

            return Json(new
            {
                html = this.RenderPartialViewToString("AjaxReturnRequestComplete"),
                message = message,
                success = true,
                complete = true
            });
        }

        [HttpPost, ActionName("ReturnRequest")]
        [ValidateInput(false)]
        public ActionResult ReturnRequestSubmit(int orderId, SubmitReturnRequestModel model, FormCollection form)
        {
            var customer = _workContext.CurrentCustomer;
            var order = _orderService.GetOrderById(orderId);

            if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
                return new HttpUnauthorizedResult();

            if (!_orderProcessingOrdersService.IsReturnRequestAllowed(order))
                return RedirectToRoute("HomePage");

            
            int count = 0;
            var rrs = new List<ReturnRequest>();
            try
            {
                PrepareReturnRequestModel(model, order);
                foreach (var opv in order.OrderProductVariants)
                {
                    int quantity = 0; //parse quantity
                    foreach (string formKey in form.AllKeys)
                        if (formKey.Equals(string.Format("quantity{0}", opv.Id), StringComparison.InvariantCultureIgnoreCase))
                        {
                            int.TryParse(form[formKey], out quantity);
                            break;
                        }
                    if (quantity > 0)
                    {
                        rrs.Add(new ReturnRequest()
                        {
                            OrderProductVariantId = opv.Id,
                            Quantity = quantity,
                            CustomerId = _workContext.CurrentCustomer.Id,
                            ReasonForReturn = model.ReturnReason,
                            RequestedAction = model.ReturnAction,
                            CustomerComments = model.Comments,
                            StaffNotes = string.Empty,
                            ReturnRequestStatus = ReturnRequestStatus.Pending,
                            CreatedOnUtc = DateTime.UtcNow,
                            UpdatedOnUtc = DateTime.UtcNow
                        });

                        //refund opv
                        opv.RefundQuantity = opv.RefundQuantity.HasValue ? (opv.RefundQuantity.Value + quantity) : quantity;
                        _orderService.UpdateOrder(order);

                        count++;
                    }
                }

                model = PrepareReturnRequestModel(model, order);
                if (count <= 0)
                    model.Result = _localizationService.GetResource("ReturnRequests.NoItemsSubmitted");
                else
                {
                    if (model.ShipmentMethodId == 1)//SportsHub Express
                    {
                        _genericAttributeService.SaveAttribute<List<ReturnRequest>>(customer, SystemCustomerAttributeNames.PendingReturnRequests, rrs);

                        return RedirectToAction("Address");
                    }
                    else
                    {
                        rrs.ForEach(x => _workContext.CurrentCustomer.ReturnRequests.Add(x));
                        _customerService.UpdateCustomer(_workContext.CurrentCustomer);

                        return RedirectToAction("ReturnRequests", "Customer", new { area = "", showResult = true });
                    }
                }
            }
            catch (Exception ex)
            {
                model.Result = ex.Message;
            }
            return View(model);
        }

        [NonAction]
        protected bool IsInterDistrict(Address address1, Address address2)
        {
            var district1 = _logisticsService.GetDistrictByZipPostalCode(address1.ZipPostalCode);
            var district2 = _logisticsService.GetDistrictByZipPostalCode(address2.ZipPostalCode);

            return (district1 != null && district2 != null && district1.Id != district2.Id);
        }

        #endregion
    }
}
